package com.rxh.utils;

import com.rxh.exception.PayException;
import com.rxh.pojo.payment.CrossCardInfo;
import com.rxh.pojo.payment.ParamRule;
import com.rxh.pojo.payment.ProductDetail;
import com.rxh.pojo.payment.TradeObject;
import com.rxh.pojo.risk.RiskOrderTrack;
import org.apache.commons.codec.digest.DigestUtils;
import com.rxh.utils.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created with IntelliJ IDEA.
 * User: 陈俊雄
 * Date: 2018/8/21
 * Time: 15:03
 * Project: Management
 * Package: com.rxh.utils
 */
public class ParseTradeInfo {
    private static final String IPV4_REGEX = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
    private static final String IPV6_REGEX = "^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$";
    // 可空
    private static final int OTHER = 0;
    // 必填项
    private static final int REQUIRED = 1;
    // 数字输入
    private static final int NUMBER = 2;
    // 金钱格式
    private static final int MONEY = 3;
    // 邮箱格式
    private static final int EMAIL = 4;
    // IP格式
    private static final int IP = 5;
    // 货物列表
    private static final int GOOD_LIST = 6;
    // 卡号
    private static final int CARD_NO = 7;
    // 有效月份
    private static final int EXPIRE_MONTH = 8;
    // 有效年份
    private static final int EXPIRE_YEAR = 9;
    // 安全码
    private static final int SECURITY_NUM = 10;
    // 报文参数的验证规则
    private final static Map<String, ParamRule> paramValidate = new HashMap<String, ParamRule>() {
        {
            // 交易数据
            put("MerNo", new ParamRule(NUMBER, 11, 1001));// 商户号
            put("BillNo", new ParamRule(REQUIRED, 100, 1002));// 商户订单号
            put("Amount", new ParamRule(MONEY, 1003));// 总金额
            put("Freight", new ParamRule(MONEY, 1004));// 运费
            put("Fee", new ParamRule(MONEY, 1005));// 手续费
            put("Currency", new ParamRule(REQUIRED, 50, 1006));// 交易币种
            // 持卡人相关信息
            put("BillFirstName", new ParamRule(REQUIRED, 100, 1007));// 持卡人姓
            put("BillLastName", new ParamRule(REQUIRED, 100, 1008));// 持卡人名
            put("BillAddress", new ParamRule(REQUIRED, 200, 1009));// 持卡人地址
            put("BillCity", new ParamRule(REQUIRED, 100, 1010));// 持卡人城市
            put("BillState", new ParamRule(OTHER, 100, 1011));// 持卡人州/省
            put("BillCountry", new ParamRule(REQUIRED, 100, 1012));// 持卡人国家
            put("BillZip", new ParamRule(REQUIRED, 50, 1013));// 持卡人邮编
            put("BillEmail", new ParamRule(EMAIL, 100, 1014));// 持卡人邮箱
            put("BillPhone", new ParamRule(REQUIRED, 50, 1015));// 持卡人电话
            // 收货人信息
            put("ShipFirstName", new ParamRule(REQUIRED, 100, 1016));// 收货人姓
            put("ShipLastName", new ParamRule(REQUIRED, 100, 1017));// 收货人名
            put("ShipAddress", new ParamRule(REQUIRED, 200, 1018));// 收货人地址
            put("ShipCity", new ParamRule(REQUIRED, 100, 1019));// 收货人城市
            put("ShipState", new ParamRule(OTHER, 100, 1020));// 收货人州/省
            put("ShipCountry", new ParamRule(REQUIRED, 100, 1021));// 收货人国家
            put("ShipZip", new ParamRule(REQUIRED, 50, 1022));// 收货邮编
            put("ShipPhone", new ParamRule(REQUIRED, 100, 1023));// 收货人电话
            put("ShipEmail", new ParamRule(EMAIL, 50, 1024));// 收货人邮箱
            // 系统参数
            put("Lang", new ParamRule(REQUIRED, 32, 1025));// 页面语言
            put("PayMode", new ParamRule(REQUIRED, 32, 1026));// 支付方式
            put("PayType", new ParamRule(OTHER, 32, 1027));// 支付类型(支付方式下级)
            put("ReturnURL", new ParamRule(REQUIRED, 200, 1028));// 返回地址(同步响应)
            put("NotifyURL", new ParamRule(REQUIRED, 200, 1029));// 通知地址(异步响应)
            // 验证参数
            put("Remark", new ParamRule(OTHER, 500, 1030));// 交易备注
            put("Md5Info", new ParamRule(REQUIRED, 150, 1031));// md5验证码
            put("Ip", new ParamRule(IP, 1032));// ip
            put("CustomerId", new ParamRule(OTHER, 200, 1033));// 用户id
            put("UserAgent", new ParamRule(OTHER, 150, 1034));// 浏览器HTTP_USER_AGENT
            // 信用卡信息
            put("CardNo", new ParamRule(CARD_NO, 14, 15, 1035));// 卡号
            put("ExpireMonth", new ParamRule(EXPIRE_MONTH, 1036));// 有效月
            put("ExpireYear", new ParamRule(EXPIRE_YEAR, 4, 1037));// 有效年
            put("SecurityNum", new ParamRule(SECURITY_NUM, 3, 4, 1038));// 安全码(3-4位数字)
            put("Issue", new ParamRule(OTHER, 200, 1039));// 发卡行
            // 货物信息
            put("GoodList", new ParamRule(GOOD_LIST, 1040));
        }
    };

    /**
     * 解析交易信息并获取交易对象
     *
     * @param tradeInfo 交易信息
     * @return 交易对象
     * @throws PayException 异常信息
     */
    public static TradeObject parse(String tradeInfo, RiskOrderTrack orderTrack) throws PayException {
        try {
            List<String> tradeInfoKeys = new ArrayList<>();
            String tradeInfoDecode = new String(Base64.getDecoder().decode(tradeInfo.getBytes()));
            Document document = DocumentHelper.parseText(tradeInfoDecode);
            Element rootElement = document.getRootElement();
            for (int i = 0; i < rootElement.nodeCount(); i++) {
                validateValue(rootElement.node(i).getName(), rootElement.node(i).getStringValue(), orderTrack.getInterType());
                tradeInfoKeys.add(rootElement.node(i).getName());
            }
            // 校验必要参数
            validateItem(tradeInfoKeys, orderTrack.getInterType());
            // 获取商品信息
            List<ProductDetail> productDetails = setProducts(rootElement);
            // 获取交易数据对象
            TradeObject tradeObject = new TradeObject();
            // 交易数据
            tradeObject.setMerNo(Integer.valueOf(rootElement.valueOf("MerNo")));
            tradeObject.setBillNo(rootElement.valueOf("BillNo"));
            tradeObject.setAmount(new BigDecimal(rootElement.valueOf("Amount")));
            tradeObject.setFreight(new BigDecimal(rootElement.valueOf("Freight")));
            tradeObject.setFee(new BigDecimal(rootElement.valueOf("Fee")));
            tradeObject.setCurrency(rootElement.valueOf("Currency"));
            // 持卡人相关信息
            tradeObject.setBillFirstName(rootElement.valueOf("BillFirstName"));
            tradeObject.setBillLastName(rootElement.valueOf("BillLastName"));
            tradeObject.setBillAddress(rootElement.valueOf("BillAddress"));
            tradeObject.setBillCity(rootElement.valueOf("BillCity"));
            tradeObject.setBillState(rootElement.valueOf("BillState"));
            tradeObject.setBillCountry(rootElement.valueOf("BillCountry"));
            tradeObject.setBillZip(rootElement.valueOf("BillZip"));
            tradeObject.setBillEmail(rootElement.valueOf("BillEmail"));
            tradeObject.setBillPhone(rootElement.valueOf("BillPhone"));
            // 收货人信息
            tradeObject.setShipFirstName(rootElement.valueOf("ShipFirstName"));
            tradeObject.setShipLastName(rootElement.valueOf("ShipLastName"));
            tradeObject.setShipAddress(rootElement.valueOf("ShipAddress"));
            tradeObject.setShipCity(rootElement.valueOf("ShipCity"));
            tradeObject.setShipState(rootElement.valueOf("ShipState"));
            tradeObject.setShipCountry(rootElement.valueOf("ShipCountry"));
            tradeObject.setShipZip(rootElement.valueOf("ShipZip"));
            tradeObject.setShipPhone(rootElement.valueOf("ShipPhone"));
            tradeObject.setShipEmail(rootElement.valueOf("ShipEmail"));
            // 系统参数
            tradeObject.setLang(rootElement.valueOf("Lang"));
            tradeObject.setPayMode(rootElement.valueOf("PayMode"));
            tradeObject.setReturnURL(rootElement.valueOf("ReturnURL"));
            tradeObject.setNotifyURL(rootElement.valueOf("NotifyURL"));
            // 验证参数
            tradeObject.setRemark(rootElement.valueOf("Remark"));
            tradeObject.setMd5Info(rootElement.valueOf("Md5Info"));
            tradeObject.setIp(rootElement.valueOf("Ip"));
            tradeObject.setCustomerId(rootElement.valueOf("CustomerId"));
            tradeObject.setUserAgent(rootElement.valueOf("UserAgent"));
            // 信用卡信息
            if (SystemConstant.INTERFACE_INNER == orderTrack.getInterType()) {
                tradeObject.setCardNo(rootElement.valueOf("CardNo"));
                tradeObject.setExpireMonth(Short.valueOf(rootElement.valueOf("ExpireMonth")));
                tradeObject.setExpireYear(Short.valueOf(rootElement.valueOf("ExpireYear")));
                tradeObject.setSecurityNum(rootElement.valueOf("SecurityNum"));
                tradeObject.setIssue(rootElement.valueOf("Issue"));
                tradeObject.setPayType(StringUtils.isBlank(rootElement.valueOf("PayType")) ?
                        getPayTypeByCardNum(tradeObject.getCardNo()) : rootElement.valueOf("PayType"));
            } else {
                tradeObject.setPayType(rootElement.valueOf("PayType"));
            }
            // 货物信息
            tradeObject.setProductDetails(productDetails);
            // 设置orderTrick属性
            orderTrack.setAmount(tradeObject.getAmount());
            orderTrack.setMerOrderId(tradeObject.getBillNo());
            orderTrack.setMerId(tradeObject.getMerNo());
            orderTrack.setCurrency(tradeObject.getCurrency());
            orderTrack.setLang(tradeObject.getLang());
            return tradeObject;
        } catch (DocumentException e) {
            e.printStackTrace();
            throw new PayException("请求报文无法解析！", 1100);
        }
    }

    public static void parse(CrossCardInfo crossCardInfo, TradeObject tradeObject) throws PayException {
        validateValue("CardNo", crossCardInfo.getCardNo(), SystemConstant.INTERFACE_REDIRECT);
        validateValue("ExpireMonth", crossCardInfo.getCardExpireMonth(), SystemConstant.INTERFACE_REDIRECT);
        validateValue("ExpireYear", crossCardInfo.getCardExpireYear(), SystemConstant.INTERFACE_REDIRECT);
        validateValue("SecurityNum", crossCardInfo.getCvv2(), SystemConstant.INTERFACE_REDIRECT);
        validateValue("Issue", crossCardInfo.getIssuingBank(), SystemConstant.INTERFACE_REDIRECT);
        validateValue("BillFirstName", crossCardInfo.getFirstName(), SystemConstant.INTERFACE_REDIRECT);
        validateValue("BillLastName", crossCardInfo.getLastName(), SystemConstant.INTERFACE_REDIRECT);
        tradeObject.setCardNo(crossCardInfo.getCardNo());
        tradeObject.setExpireMonth(Short.valueOf(crossCardInfo.getCardExpireMonth()));
        tradeObject.setExpireYear(Short.valueOf(crossCardInfo.getCardExpireYear()));
        tradeObject.setSecurityNum(crossCardInfo.getCvv2());
        tradeObject.setIssue(crossCardInfo.getIssuingBank());
        tradeObject.setBillFirstName(crossCardInfo.getFirstName());
        tradeObject.setBillLastName(crossCardInfo.getLastName());
    }

    private static void validateValue(String key, String value, int interType) throws PayException {
        ParamRule paramRule = paramValidate.get(key);
        if (paramRule == null) {
            return;
        }
        switch (paramRule.getType()) {
            case OTHER:
                if (value.length() > paramRule.getMaxLength()) {
                    throw new PayException("报文参数：" + key + " 超出长度：" + paramRule.getMaxLength(), paramRule.getExceptionCode());
                }
                break;
            case REQUIRED:
                if (StringUtils.isBlank(value)) {
                    throw new PayException("报文参数：" + key + " 不能为空", paramRule.getExceptionCode());
                } else if (value.length() > paramRule.getMaxLength()) {
                    throw new PayException("报文参数：" + key + " 超出长度：" + paramRule.getMaxLength(), paramRule.getExceptionCode());
                }
                break;
            case NUMBER:
                if (!value.matches("^\\d{1," + paramRule.getMaxLength() + "}$")) {
                    throw new PayException("报文参数：" + key + " 数字格式不正确，错误值为：" + value, paramRule.getExceptionCode());
                }
                break;
            case MONEY:
                if (!value.matches("^\\d{1,10}$|^.\\d{0,2}$|^\\d{1,10}.\\d{0,2}")) {
                    throw new PayException("报文参数：" + key + " 金额格式不正确，错误值为：" + value, paramRule.getExceptionCode());
                }
                break;
            case EMAIL:
                if (!value.matches("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?")) {
                    throw new PayException("报文参数：" + key + " 邮箱格式不正确，错误值为：" + value, paramRule.getExceptionCode());
                }
                break;
            case IP:
                if (!value.matches(IPV4_REGEX) && !value.matches(IPV6_REGEX)) {
                    throw new PayException("报文参数：" + key + " 格式不正确，错误值为：" + value, paramRule.getExceptionCode());
                }
                break;
            case GOOD_LIST:
                if (StringUtils.isBlank(value)) {
                    throw new PayException("报文参数：" + key + " 货物列表为空！", paramRule.getExceptionCode());
                }
                break;
            case CARD_NO:
                if (SystemConstant.INTERFACE_INNER == interType && !value.matches("^[345]\\d{" + paramRule.getMinLength() + "," + paramRule.getMaxLength() + "}$")) {
                    throw new PayException("卡号格式错误，错误值为：" + value, paramRule.getExceptionCode());
                }
                break;
            case EXPIRE_MONTH:
                if (SystemConstant.INTERFACE_INNER == interType && !value.matches("^1[0-2]$|^[1-9]$|0[1-2]")) {
                    throw new PayException("有效月格式错误，错误值为：" + value, paramRule.getExceptionCode());
                }
                break;
            case EXPIRE_YEAR:
                if (SystemConstant.INTERFACE_INNER == interType && !value.matches("^\\d{1," + paramRule.getMaxLength() + "}$")) {
                    throw new PayException("有效年格式错误，错误值为：" + value, paramRule.getExceptionCode());
                }
                break;
            case SECURITY_NUM:
                if (SystemConstant.INTERFACE_INNER == interType && !value.matches("^\\d{" + paramRule.getMinLength() + "," + paramRule.getMaxLength() + "}$")) {
                    throw new PayException("安全码格式错误，错误值为：" + value, paramRule.getExceptionCode());
                }
                break;
            default:
                break;
        }
    }

    private static void validateItem(List<String> tradeInfoKeys, int interType) throws PayException {
        List<String> requiredKeys = new ArrayList<>();
        paramValidate.forEach((s, paramRule) -> {
            if (paramRule.getType() != OTHER) {
                if (SystemConstant.INTERFACE_INNER != interType && paramRule.getType() < CARD_NO) {
                    requiredKeys.add(s);
                } else if (SystemConstant.INTERFACE_INNER == interType) {
                    requiredKeys.add(s);
                }
            }
        });
        if (SystemConstant.INTERFACE_REDIRECT == interType) {
            requiredKeys.remove("");
        }
        List<String> differenceSet = requiredKeys.stream().filter(s -> !tradeInfoKeys.contains(s)).collect(Collectors.toList());
        if (differenceSet.size() != 0) {
            throw new PayException("报文缺少必要参数：" + differenceSet.toString(), 1200, differenceSet.toString());
        }
    }

    private static List<ProductDetail> setProducts(Element rootElement) throws PayException {
        List<ProductDetail> productDetails = new ArrayList<>();
        Element goodListElement = rootElement.element("GoodList");
        List goodsElements = goodListElement.elements();
        for (Object o : goodsElements) {
            Element goodsElement = (Element) o;
            if (StringUtils.isBlank(goodsElement.valueOf("GoodId"))) {
                throw new PayException("商品参数：GoodId 为空！", 1041);
            }
            if (StringUtils.isBlank(goodsElement.valueOf("GoodName"))) {
                throw new PayException("商品参数：GoodName 为空！", 1042);
            }
            if (StringUtils.isBlank(goodsElement.valueOf("Qty"))) {
                throw new PayException("商品参数：Qty 为空！", 1043);
            }
            if (StringUtils.isBlank(goodsElement.valueOf("Price"))) {
                throw new PayException("商品参数：Price 为空！", 1044);
            }
            productDetails.add(new ProductDetail(goodsElement.valueOf("GoodId"),
                    goodsElement.valueOf("GoodName"),
                    new BigDecimal(goodsElement.valueOf("Price")),
                    Integer.parseInt(goodsElement.valueOf("Qty"))));
        }
        return productDetails;
    }

    public static void checkMd5(TradeObject tradeObject, String secretKey) throws PayException {
        String str = String.valueOf(tradeObject.getMerNo()) +
                tradeObject.getBillNo() +
                tradeObject.getFreight() +
                tradeObject.getAmount() +
                tradeObject.getFee() +
                tradeObject.getCurrency() +
                tradeObject.getReturnURL() +
                tradeObject.getBillEmail() +
                secretKey;
        if (!StringUtils.equalsAnyIgnoreCase(tradeObject.getMd5Info(), DigestUtils.md5Hex(str))) {
            throw new PayException("MD5Info校验错误！", 1300);
        }
    }

    private static String getPayTypeByCardNum(String cardNum) throws PayException {
        if (cardNum.startsWith("4")) {
            return SystemConstant.CREDIT_VISA;
        } else if (cardNum.startsWith("5")) {
            return SystemConstant.CREDIT_MASTER;
        } else if (cardNum.startsWith("35") || cardNum.startsWith("2131") || cardNum.startsWith("1800")) {
            return SystemConstant.CREDIT_JCB;
        } else if (cardNum.startsWith("34") || cardNum.startsWith("37")) {
            return SystemConstant.CREDIT_AE;
        } else {
            throw new PayException("无法识别卡号：" + cardNum + "信用卡卡种！", 1041);
        }
    }
}